package com.cloudtec.yunzonedep.config.shiro;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.cloudtec.yunzonedep.common.entity.ConstantPool;
import com.cloudtec.yunzonedep.common.util.JWTUtil;
import com.cloudtec.yunzonedep.common.util.RedisUtil;
import com.cloudtec.yunzonedep.modules.user.entity.SysUser;
import com.cloudtec.yunzonedep.modules.user.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.HashSet;
import java.util.Set;

@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    public String getName() {
        return "shiroRealm";
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("————身份认证————");
        String token = (String) authenticationToken.getCredentials();
        //log.info("获取token："+token);
        // 校验token有效性
        SysUser loginUser = this.checkUserTokenIsEffect(token);
        return new SimpleAuthenticationInfo(loginUser, token, getName());
    }

    /*
    * 校验token的有效性
    * 1、是否有token
    * 2、是否为有效token
    * 3、缓存中是否有token，如果缓存中都没有token，直接滚去登录(说明超过一个小时没有操作过了，自动失效)
    * 4、校验token，通过token中提取的用户名，检验用户是否存在，如果不存在，滚去登录
    * 5、判断用户状态，如果被锁定，则滚去登录
    *
    * 以上都通过之后，可以认为是通过认证了。
    * 这里涉及到一个token过期的问题，如果token过期了，但是缓存中依然存在，
    * 其实仍然不应该重新登录，而是通过response的header返回最新的token
    *
    *
    * */
    public SysUser checkUserTokenIsEffect(String token) throws AuthenticationException {
        log.info("来到这里了吗");
        if (token == null) {
            throw new AuthenticationException("1001-token为空!");
        }
        // 解密获得username，用于和数据库进行对比
        String username = JWTUtil.getUsername(token);
        //如果通过token获取不到username，则抛出异常，token非法无效
        if (username == null) {
            throw new AuthenticationException("1002-token非法无效!");
        }
        //通过用户名去拿缓存中的token
        Object cacheToken = redisUtil.get(username + ConstantPool.LOGINTAG + token);
        if (StringUtils.isEmpty(cacheToken) ) {
            throw new AuthenticationException("1003-缓存中无token");
        }
        SysUser sysUser = sysUserService.getUserByName(username);
        if (sysUser == null) {
            throw new AuthenticationException("1004-用户不存在!");
        }
        // 判断用户状态
        if (sysUser.getStatus() != 1) {
            throw new AuthenticationException("1005-账号已被锁定,请联系管理员!");
        }
        /*if (!JWTUtil.verify(token, username, sysUser.getPassword())) {//使用的是密文密码
            throw new AuthenticationException("1006-token校验失败");
        }*/
        // 校验token是否超时失效 & 或者账号密码是否错误
        /*if (!jwtTokenRefresh(token, username, sysUser.getPassword())) {
            throw new AuthenticationException("1004-Token无效，请重新登录!");
        }*/
        // 查询用户信息
        SysUser loginUser = new SysUser();
        BeanUtils.copyProperties(sysUser, loginUser);
        return loginUser;
    }

    /**
     * JWTToken刷新生命周期 （解决用户一直在线操作，提供Token失效问题）
     * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)
     * 2、当该用户再次请求时，通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
     * 3、当该用户这次请求JWTToken值还在生命周期内，则会通过重新PUT的方式k、v都为Token值，缓存中的token值生命周期时间重新计算(这时候k、v值一样)
     * 4、当该用户这次请求jwt生成的token值已经超时，但该token对应cache中的k还是存在，则表示该用户一直在操作只是JWT的token失效了，程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值，该缓存生命周期重新计算
     * 5、当该用户这次请求jwt在生成的token值已经超时，并在cache中不存在对应的k，则表示该用户账户空闲超时，返回用户信息已失效，请重新登录。
     * 6、每次当返回为true情况下，都会给Response的Header中设置Authorization，该Authorization映射的v为cache对应的v值。
     * 7、注：当前端接收到Response的Header中的Authorization值会存储起来，作为以后请求token使用
     * 参考方案：https://blog.csdn.net/qq394829044/article/details/82763936
     *
     * @param userName
     * @param passWord
     * @return
     */
    /*public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        Object cacheToken = redisUtil.get(userName + ConstantPool.LOGINTAG + token)+"";
        if (!StringUtils.isEmpty(cacheToken)) {
            // 校验token有效性
            if (!JWTUtil.verify(token, userName, passWord)) {
                System.out.println("token无效了");
                //如果无效了，重新生成一个token
                String newToken = JWTUtil.sign(userName, passWord);
                redisUtil.set(userName + ConstantPool.LOGINTAG + token, newToken);
                // 设置超时时间
                redisUtil.expire(userName + ConstantPool.LOGINTAG + token, JWTUtil.EXPIRE_TIME / 1000);
            } else {
                System.out.println("token依然有效");
                //如果依然有效，重新放入，相当更新一下，相当于把过期时间重置了一下，依然是60min
                redisUtil.set(userName + ConstantPool.LOGINTAG + token, cacheToken);
                // 设置超时时间
                redisUtil.expire(userName + ConstantPool.LOGINTAG + token, JWTUtil.EXPIRE_TIME / 1000);
            }
            return true;
        }
        return false;
    }*/


    /**
     * 功能： 获取用户权限信息，包括角色以及权限。只有当触发检测用户权限时才会调用此方法，例如checkRole,checkPermission
     *
     *
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("————权限认证 [ roles、permissions]————");
        SysUser sysUser = null;
        String username = null;
        if (principalCollection != null) {
            sysUser = (SysUser) principalCollection.getPrimaryPrincipal();
            username = sysUser.getUsername();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 设置用户拥有的角色集合，比如“admin,test”
        //Set<String> roleSet = sysUserService.getUserRolesSet(username);
        Set<String> roleSet = new HashSet<String>();
        //roleSet.add("admin");
        //roleSet.add("manager");
        info.setRoles(roleSet);

        // 设置用户拥有的权限集合，比如“sys:role:add,sys:user:add”
        //Set<String> permissionSet = sysUserService.getUserPermissionsSet(username);
        Set<String> permissionSet= new HashSet<String>();
        //permissionSet.add("add");
        //permissionSet.add("edit");
        info.addStringPermissions(permissionSet);
        return info;
    }

}
